package com.weipt.juc.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author peter.wei 2020/09/09 16:22
 * @version 1.0
 * @company CBI
 * @description <br>peter.wei 2020/09/09 16:22
 **/
public class HelloLock {
    public static void main(String[] args) {
        Ticket2 ticket = new Ticket2();

        //所谓的并发，就是多个线程操作同一资源
        //资源就是票，此处利用lambda实现
        new Thread(ticket::saleTicket, "小明").start(); //JDK8的一种语法糖。
        new Thread(() -> ticket.saleTicket(), "小黑").start(); //lambda
        new Thread(new Runnable() {     //匿名内部类
            @Override
            public void run() {
                ticket.saleTicket();
            }
        }, "小红").start();
    }
}


//按照java oop思想，类就是单纯做自己的事情，不要去实现runnable
//实现最大限度的解耦
class Ticket2{
    public int ticket_num = 50;
    public void saleTicket(){
        //程序可以被安全的并行执行，就是可重入的
        //不可重入锁，也叫自旋锁（spin lock），是一种非阻塞锁，也就是说，如果某线程需要获取锁，但该锁已经被其他线程占用时，该线程不会被挂起，而是在不断的消耗CPU的时间，不停的试图获取锁。
        //可重入就意味着：线程可以进入任何一个它已经拥有的锁所同步着的代码块。
        //第一个线程执行print()方法，得到了锁，使lockedBy等于当前线程，也就是说，执行的这个方法的线程获得了这个锁，执行add()方法时，
        // 同样要先获得锁，因不满足while循环的条件，也就是不等待，继续进行，将此时的lockedCount变量，也就是当前获得锁的数量加一，
        // 当释放了所有的锁，才执行notify()。如果在执行这个方法时，有第二个线程想要执行这个方法，因为lockedBy不等于第二个线程，
        // 导致这个线程进入了循环，也就是等待，不断执行wait()方法。只有当第一个线程释放了所有的锁，执行了notify()方法，第二个线程才得以跳出循环，继续执行。

        //互斥锁有一个缺点，他的执行流程是这样的 托管代码  - 用户态代码 - 内核态代码、上下文切换开销与损耗，假如获取到资源锁的线程A立马处理完逻辑释放掉资源锁，
        //如果是采取互斥的方式，那么线程B从没有获取锁到获取锁这个过程中，就要用户态和内核态调度、上下文切换的开销和损耗。所以就有了自旋锁的模式，让线程B就在用户态循环等着，减少消耗。
        final ReentrantLock lock = new ReentrantLock();//可重入的意思是某一个线程是否可多次获得一个锁，比如synchronized就是可重入的，ReentrantLock也是可重入的
        final ReentrantLock unfairLock = new ReentrantLock(false); //非公平，其实就是为了公平，方式后面排队的人执行能力强
        final ReentrantLock fairLock = new ReentrantLock(true);   // 公平锁

        //ReentrantReadWriteLock
        while (true){
            if (ticket_num <= 0){
                return;
            }
            /**在使用阻塞等待获取锁的方式中，必须在try代码块之外，并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用，避免加锁成功后，在finally中无法解锁。
            说明一：如果在lock方法与try代码块之间的方法调用抛出异常，那么无法解锁，造成其它线程无法成功获取锁。
            说明二：如果lock方法在try代码块之内，可能由于其它方法抛出异常，导致在finally代码块中，unlock对未加锁的对象解锁，它会调用AQS的tryRelease方法（取决于具体实现类），抛出IllegalMonitorStateException异常。
            说明三：在Lock对象的lock方法实现中可能抛出unchecked异常，产生的后果与说明二相同。'*/
            lock.lock();  //接口，更加灵活
            //lock.tryLock(); //不断尝试
            //lock.tryLock(10000, TimeUnit.SECONDS);  //1000s尝试
            /**在ReentrantLock 中，lock()方法是一个无条件的锁，与synchronize意思差不多，但是另一个方法 tryLock()方法只有在成功获取了锁的情况下才会返回true，
            如果别的线程当前正持有锁，则会立即返回false！如果为这个方法加上timeout参数，则会在等待timeout的时间才会返回false或者在获取到锁的时候返回true。*/

            /**1:  lock拿不到锁会一直等待。tryLock是去尝试，拿不到就返回false，拿到返回true。然后继续执行下方逻辑
            2:  tryLock是可以被打断的，被中断 的，lock是不可以。*/
            try {
                System.out.println(Thread.currentThread().getName()+"当前正在购买："+ ticket_num--+"剩余："+ticket_num);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}